package com.googlecode.connectlet.crypto.random;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import com.googlecode.connectlet.crypto.digest.MD5Stream;
import com.googlecode.connectlet.crypto.stream.RC4Cipher;
import com.googlecode.connectlet.util.Bytes;
import com.googlecode.connectlet.util.ClassPath;

public class RC4PrngCipher extends RC4Cipher {
	private static final long serialVersionUID = 1L;

	private static ClassPath file = ClassPath.getInstance("../data/.rnd");
	private static MD5Stream md5 = new MD5Stream();
	private static volatile RC4PrngCipher instance = null;

	public static RC4PrngCipher getInstance() {
		if (instance == null) {
			synchronized (RC4PrngCipher.class) {
				if (instance == null) {
					instance = new RC4PrngCipher();
				}
			}
		}
		return instance;
	}

	private synchronized static byte[] getSeed() {
		byte[] seed = new byte[32];
		try {
			if (!file.exists()) {
				file.createNewFile();
			}
			FileInputStream in = new FileInputStream(file);
			in.read(seed);
			in.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		long milliTime = System.currentTimeMillis();
		long nanoTime = System.nanoTime();
		byte[] seed1 = md5.digest(Bytes.fromLong(milliTime));
		byte[] seed2 = md5.digest(Bytes.fromLong(nanoTime));
		for (int i = 0; i < 16; i ++) {
			seed[i] ^= seed1[i];
			seed[16 + i] ^= seed2[i];
		}
		byte[] result = Bytes.clone(seed);
		seed1 = md5.digest(Bytes.fromLong(nanoTime, Bytes.LITTLE_ENDIAN));
		seed2 = md5.digest(Bytes.fromLong(milliTime, Bytes.LITTLE_ENDIAN));
		for (int i = 0; i < 16; i ++) {
			seed[i] ^= seed1[i];
			seed[16 + i] ^= seed2[i];
		}
		try {
			FileOutputStream out = new FileOutputStream(file);
			out.write(seed);
			out.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return result;
	}

	private RC4PrngCipher() {
		super(getSeed());
	}

	@Override
	public synchronized int output() {
		return super.output();
	}

	@Override
	public synchronized void output(byte[] b, int off, int len) {
		for (int i = 0; i < len; i ++) {
			b[off + i] = (byte) super.output();
		}
	}

	@Deprecated
	@Override
	public int update(int b) {
		throw new UnsupportedOperationException();
	}

	@Deprecated
	@Override
	public void update(byte[] src, int srcPos, byte[] dest, int destPos, int length) {
		throw new UnsupportedOperationException();
	}

	@Deprecated
	@Override
	public OutputStream encrypt(final OutputStream out) {
		throw new UnsupportedOperationException();
	}

	@Deprecated
	@Override
	public InputStream decrypt(final InputStream in) {
		throw new UnsupportedOperationException();
	}
}